<template>
  <div>
    <div class="legend">
      <div class="legend-item">
        <div class="color-box player"></div> 玩家
      </div>
      <div class="legend-item">
        <div class="color-box npc"></div> NPC
      </div>
      <div class="legend-item">
        <div class="color-box fishing"></div> 钓鱼点
      </div>
      <div class="legend-item">
        <div class="color-box obstacle"></div> 障碍物
      </div>
      <div class="legend-item">
        <div class="color-box empty"></div> 空地
      </div>
    </div>
    <div class="map">
      <div class="grid-container">
        <div v-for="(cell, index) in grid" :key="index" :ref="el => cellRefs[index] = el"
          :class="['grid-item', cell.type, `x-${index % gridSize}`, `y-${Math.floor(index / gridSize)}`]"
          :style="{ backgroundColor: cell.his }" @click="gridInfo(index, cell)" />
      </div>
    </div>
    <div class="controls">
      <el-button class="home-button" @click="move('q')">
        回家<span class="shortcutKeys">(Q)</span>
      </el-button>
      <el-button class="up-button" @click="move('up')" :disabled="isTopObstacle || playerY == 0">
        往北<span class="shortcutKeys">(W)</span>
      </el-button>
      <el-button class="dialogue-button" @click="move('e')" :disabled="!isNpc && !isFishing">
        {{ isNpc || !isFishing ? '对话' : '钓鱼' }}<span class="shortcutKeys">(E)</span>
      </el-button>
      <el-button class="left-button" @click="move('left')" :disabled="isLeftObstacle || playerX === 0">
        往西<span class="shortcutKeys">(A)</span>
      </el-button>
      <el-button class="down-button" @click="move('down')" :disabled="isDownObstacle || playerY === gridSize - 1">
        往南<span class="shortcutKeys">(S)</span>
      </el-button>
      <el-button class="right-button" @click="move('right')" :disabled="isRightObstacle || playerX === gridSize - 1">
        往东<span class="shortcutKeys">(D)</span>
      </el-button>
    </div>
    <el-drawer v-model="fishingShow" @close="endGame" title="钓鱼" direction="rtl" class="strengthen">
      <div class="game-container">
        <div class="time">倒计时: {{ fishing.timeLeft }}秒</div>
        <div class="outer-wrapper">
          <div class="progress-bar-container">
            <div class="progress-bar" ref="progressBar" :style="{ height: fishing.progressPercentage }" />
            <div class="progress-text">{{ fishing.progressText }}%</div>
          </div>
          <div class="outer-container" ref="outerContainer"
            :style="{ border: fishing.overlap ? '2px solid var(--el-color-warning)' : '2px solid var(--el-border-color)' }">
            <div class="moving-container" ref="movingContainer"
              :style="{ animation: fishing.overlap ? 'wobble .5s ease-in-out infinite' : '', transformorigin: fishing.overlap ? 'bottom center' : '' }">
              <svg viewBox="0 0 1024 1024">
                <path
                  d="M942 619.8c-27.4-29.4-42.5-67.6-42.5-107.8s15.1-78.4 42.5-107.8c14.8-15.8 18.6-38 10-58-8.6-19.8-27.3-32.2-48.9-32.3h-0.9c-35.3 0-70 9.4-100.2 27.2-29.4 17.3-53.9 41.9-71.1 71.4-2.8 4.8-7.3 6-9.7 6.4-4.2 0.6-8.4-0.8-11.3-3.8-38.3-38.2-82.9-68.2-132.5-89.1-51.4-21.7-105.9-32.7-162.2-32.7-29.1 0-57.6 2.9-85.5 8.7l-1.1 0.3c-33.5 7.1-66 18.3-97.1 33.6-28.6 14.1-55.7 31.5-80.5 51.8l-0.2 0.2c-2.1 1.7-4.1 3.4-6.2 5.2-0.4 0.4-0.9 0.8-1.3 1.1l-4.5 3.9c-0.6 0.5-1.2 1.1-1.8 1.6-1.7 1.5-3.3 3-5 4.5-0.8 0.7-1.6 1.5-2.4 2.3-1 1-2.1 2-3.1 2.9-0.8 0.8-1.6 1.5-2.4 2.3l-4.5 4.5c-0.8 0.8-1.6 1.6-2.4 2.5-1 1.1-2.1 2.1-3.1 3.2-0.7 0.8-1.4 1.5-2.2 2.3l-4.5 4.8c-0.6 0.7-1.3 1.4-1.9 2.1-1.3 1.5-2.7 3-4 4.5-0.3 0.4-0.7 0.8-1 1.1-1.6 1.9-3.3 3.8-4.9 5.7-0.4 0.5-0.9 1-1.3 1.6-1.7 2-3.3 4-4.9 6-14.1 17.6-21.9 39.7-21.9 62.2s7.8 44.6 21.9 62.2c1.6 2 3.3 4 4.9 6 0.4 0.5 0.9 1 1.3 1.6 1.6 1.9 3.2 3.8 4.9 5.7 0.3 0.4 0.7 0.7 1 1.1 1.3 1.5 2.7 3 4 4.5 0.6 0.7 1.3 1.4 1.9 2.1 1.5 1.6 3 3.3 4.6 4.9 0.7 0.7 1.4 1.5 2.1 2.2 1 1.1 2.1 2.2 3.1 3.2l2.4 2.4c1.5 1.5 3 3 4.6 4.5 0.8 0.8 1.6 1.5 2.4 2.3 1 1 2.1 2 3.1 3 0.8 0.7 1.6 1.5 2.4 2.2 1.7 1.5 3.3 3 5 4.5 0.6 0.5 1.2 1.1 1.8 1.6l4.5 3.9c0.4 0.4 0.9 0.7 1.3 1.1 2.1 1.8 4.1 3.5 6.2 5.2 0.1 0 0.1 0.1 0.2 0.1 24.8 20.3 51.9 37.8 80.5 51.9 31.2 15.3 63.7 26.5 97.1 33.6l1.1 0.3c27.8 5.8 56.4 8.7 85.5 8.7 56.2 0 110.8-11 162.2-32.7 49.6-20.9 94.2-50.9 132.5-89.1 3-3 7.1-4.3 11.3-3.8 2.4 0.3 6.9 1.6 9.7 6.4 17.1 29.4 41.7 54.1 71.1 71.4 30.3 17.8 64.9 27.2 100.2 27.2h0.9c21.5-0.1 40.3-12.5 48.8-32.3 8.6-20.3 4.8-42.5-10-58.3z m-619.8 59.4c-76.3-19.4-145.4-62.9-196.3-123.6-0.3-0.4-0.6-0.7-0.9-1.1-1.5-1.8-2.9-3.5-4.4-5.3 0 0 0-0.1-0.1-0.1-11.5-14.4-15.5-32.4-11.9-49.4 0.3-1.6 0.8-3.2 1.2-4.8s1-3.2 1.6-4.7c2.2-5.4 5.2-10.6 9-15.4 1.5-1.8 3-3.7 4.5-5.5 0.3-0.3 0.6-0.7 0.9-1 50.9-60.7 120.1-104.2 196.3-123.6 54.7 38.3 87.1 100.2 87.1 167.2 0.1 67.2-32.3 129.1-87 167.3z m593.1-17.3c-1.1 2.5-4.4 8.2-12.3 8.2h-0.8c-28.2 0-55.8-7.5-80-21.7-23.5-13.8-43.1-33.5-56.8-57-8.3-14.3-22.6-23.7-39.1-25.9-16.6-2.2-32.9 3.3-44.8 15.1-71.3 71-165.9 110.2-266.4 110.2-13.9 0-27.8-0.8-41.4-2.3 47.9-45.6 75.5-108.9 75.5-176.6s-27.6-131-75.5-176.6c13.7-1.5 27.5-2.3 41.4-2.3 100.5 0 195.2 39.1 266.4 110.2 11.8 11.8 28.1 17.3 44.8 15.1 16.5-2.2 30.8-11.6 39.1-25.9 13.7-23.5 33.3-43.2 56.8-57 24.1-14.2 51.8-21.7 80-21.7h0.7c7.9 0 11.2 5.7 12.3 8.2s3 8.9-2.5 14.8c-34.3 36.8-53.2 84.8-53.2 135.1s18.9 98.3 53.2 135.1c5.6 6.1 3.7 12.5 2.6 15z"
                  :fill="fishing.overlap ? 'var(--el-color-warning)' : 'var(--el-border-color)'" />
                <path d="M273 515.4m-30 0a30 30 0 1 0 60 0 30 30 0 1 0-60 0Z"
                  :fill="fishing.overlap ? 'var(--el-color-warning)' : 'var(--el-border-color)'" />
              </svg>
            </div>
            <div class="inner-container" ref="innerContainer">
              <svg viewBox="0 0 1024 1024">
                <path d="M893.456828 709.055005"
                  :fill="fishing.overlap ? 'var(--el-color-warning)' : 'var(--el-border-color)'" />
                <path d="M491.889987 337.939709"
                  :fill="fishing.overlap ? 'var(--el-color-warning)' : 'var(--el-border-color)'" />
                <path d="M568.154951 338.993714"
                  :fill="fishing.overlap ? 'var(--el-color-warning)' : 'var(--el-border-color)'" />
                <path
                  d="M789.271978 570.404178c-10.123568-32.030507-33.907251-88.211041-88.099501-128.403438 59.974953 92.142583 39.444361 191.201693 16.587793 231.574193 19.583011-9.214872 48.428989-39.642882 48.428989-39.642882s5.043876 37.219693-1.590218 81.112366c-2.619664 2.876514-4.52199 6.466272-5.253654 10.576893-14.963807 83.60514-81.932034 172.931584-216.578664 172.931584-129.71634 0-226.874147-110.219287-226.874147-262.079974L315.892577 167.967526c0-1.240247 2.843768-2.440585 2.641153-3.623527 24.40483-7.115048 43.811832-29.649274 43.811832-56.31561 0-32.355919-25.581632-58.68968-57.93755-58.68968s-58.313104 26.332738-58.313104 58.68968c0 23.514553 11.484565 43.790342 31.504528 53.148477-0.724501 2.143826-3.66139 4.402263-3.66139 6.791683l0 468.503347c0 176.588881 114.748441 304.763099 269.192975 304.763099 130.210597 0 230.4066-78.81709 255.399831-201.286376 2.036379-0.156566 4.120854-0.963954 4.539386-2.777253C817.526485 674.573679 802.432719 618.005312 789.271978 570.404178zM305.160141 81.350796c14.71412 0 26.676569 11.962449 26.676569 26.676569s-11.962449 26.676569-26.676569 26.676569-26.676569-11.962449-26.676569-26.676569S290.446022 81.350796 305.160141 81.350796z"
                  :fill="fishing.overlap ? 'var(--el-color-warning)' : 'var(--el-border-color)'" />
              </svg>
            </div>
          </div>
        </div>
        <el-button @touchstart="startFishing" @touchend="stopFishing" @mousedown="startFishing" @mouseup="stopFishing"
          @mouseleave="stopFishing" @click="moveButton('click')" :disabled="fishing.disabled" class="button">
          钓鱼<span class="diaoyu-shortcutKeys">({{ holdingId ? '松开' : '长按' }})</span>
        </el-button>
      </div>
    </el-drawer>
    <el-drawer v-model="npcShow" :title="npcInfo.name" direction="rtl" class="strengthen">
      <div class="wife-box">
        <div class="attributes">
          <div class="attribute-box">
            <div class="tag attribute">
              境界: {{ levelNames(npcInfo.lv) }} ({{ npcInfo.reincarnation }}转)
            </div>
            <div class="tag attribute">
              气血: 不详
            </div>
            <div class="tag attribute">
              攻击: 不详
            </div>
            <div class="tag attribute">
              防御: 不详
            </div>
            <div class="tag attribute">
              闪避: 不详
            </div>
            <div class="tag attribute">
              暴击: 不详
            </div>
            <div class="tag attribute">
              好感度: {{ npcInfo.favorability }}
            </div>
          </div>
        </div>
        <div class="click-box">
          <el-button type="primary" @click="giftShow = !giftShow">
            {{ giftShow ? '取消赠送' : '赠送礼物' }}
          </el-button>
          <el-button type="primary" :disabled="npcInfo.favorability < 1000 || isHaveWife(npcInfo.name)"
            @click="harvestNpc(npcInfo)">
            {{ isHaveWife(npcInfo.name) ? '已结为道侣' : '结为道侣' }}
          </el-button>
        </div>
        <div class="gift-box" v-if="giftShow">
          <div class="gift-item" v-for="(item, index) in giftItems" :key="index" @click="giftInfo(item, index)">
            <tag :type="item.lv">{{ item.name }}</tag>
            <span class="gift-name">{{ item.price }}灵石</span>
          </div>
        </div>
      </div>
    </el-drawer>
  </div>
</template>

<script setup>
// tag组件
import tag from '@/components/tag.vue'
// npc
import npc from '@/plugins/npc'
// 怪物
import monster from '@/plugins/monster'
import { useRouter } from 'vue-router'
import { ref, computed, onUnmounted, onMounted, nextTick } from 'vue'
import { useMainStore } from '@/plugins/store'
import { ElMessageBox } from 'element-plus'
import { levelNames, gameNotifys } from '@/plugins/game'
import MinHeap from '@/plugins/minheap'

const router = useRouter()
const store = useMainStore()
// 保存每个格子的数据
const grid = ref([])
// 玩家数据
const player = ref(store.player)
// 地图数据
const mapData = ref(store.mapData)
// 玩家在X轴的位置
const playerX = ref(0)
// 玩家在Y轴的位置
const playerY = ref(0)
const npcInfo = ref({})
// 钓鱼相关
const fishing = ref({
  // 分数
  score: 0,
  bottom: '0px',
  // 是否重叠
  overlap: false,
  // 存储定时
  timerIds: [],
  // 剩余时间
  timeLeft: 60,
  // 游戏结束
  gameOver: false,
  // 禁用按钮
  disabled: false,
  // 时间结束
  timeExpired: false,
  // 进度
  progressText: 0,
  // 内部容器位置
  innerPosition: 0,
  // 向上移动的动画ID
  moveUpAnimationId: '',
  // 进度条百分比
  progressPercentage: '0%',
  // 向下移动的动画ID
  moveDownAnimationId: '',
  // 父容器的动画ID
  moveParentContainerId: ''
})
const npcShow = ref(false)
// 地图的大小
const gridSize = ref(50)
const giftShow = ref(false)
// NPC的数量
const npcCount = ref(10)
// 是否正在长按
const holdingId = ref(null)
// 钓鱼点坐标
const fishingMap = ref([])
// 钓鱼弹窗
const fishingShow = ref(false)
// 障碍物的数量
const obstacleCount = ref(0)
const cellRefs = ref([])
const movingContainer = ref(null)
const outerContainer = ref(null)
const innerContainer = ref(null)
// 礼物列表
const giftItems = computed(() => {
  const arr = []
  const num = {
    1: '一', 2: '二', 3: '三',
    4: '四', 5: '五', 6: '六'
  }
  const lv = {
    1: 'info', 2: 'success', 3: 'primary',
    4: 'purple', 5: 'warning', 6: 'danger'
  }
  const price = {
    1: 200, 2: 400, 3: 800,
    4: 1500, 5: 2000, 6: 3000
  }
  for (let i = 1; i < 7; i++) {
    arr.push({
      lv: lv[i],
      plus: price[i] / 100,
      name: `${num[i]}品驻颜丹`,
      price: price[i]
    })
  }
  return arr
})

const totalCells = computed(() => {
  return gridSize.value * gridSize.value
})

const nearbyIndices = computed(() => {
  return [
    (playerY.value - 1) * gridSize.value + playerX.value, // 上
    (playerY.value + 1) * gridSize.value + playerX.value, // 下
    playerY.value * gridSize.value + (playerX.value - 1), // 左
    playerY.value * gridSize.value + (playerX.value + 1)  // 右
  ]
})

// 计算玩家附近是否有NPC
const isNpc = computed(() => {
  return nearbyIndices.value.some(index => grid.value[index]?.type === 'npc')
})

// 计算玩家附近是否有钓鱼点
const isFishing = computed(() => {
  return nearbyIndices.value.some(index => grid.value[index]?.type === 'fishing')
})

// 判断玩家上方有没有障碍物
const isTopObstacle = computed(() => {
  return checkDirection('up')
})

// 判断玩家左方有没有障碍物
const isLeftObstacle = computed(() => {
  return checkDirection('left')
})

// 判断玩家下方有没有障碍物
const isDownObstacle = computed(() => {
  return checkDirection('down')
})

// 判断玩家右方有没有障碍物
const isRightObstacle = computed(() => {
  return checkDirection('right')
})

// 回家
const goHome = () => {
  router.push('/home')
  if (playerX.value !== 0 || playerY.value !== 0) {
    store.mapData = { y: 0, x: 0, map: [] }
    // 重置所有钓鱼数据
    resetFishingData(false)
    // 重置玩家所在行
    store.mapScroll = 0
  }
}

// 清空所有定时
const clearAllTiming = () => {
  fishing.value.timerIds.forEach(id => clearInterval(id))
}

// 初始化地图
const initializeGrid = () => {
  // 生成地图
  grid.value = Array(totalCells.value).fill().map(() => ({ his: '', type: 'empty' }))
  const safeZone = createSafeZone()
  // 障碍物的数量
  obstacleCount.value = Math.floor(totalCells.value * 0.1)
  // 生成障碍物
  generateItems('obstacle', obstacleCount.value, safeZone)
  // 确保障碍物生成不会堵塞路径
  ensurePathAvailability()
  // 生成钓鱼点
  generateItems('fishing', 10, safeZone)
  // 确保钓鱼点生成不会堵塞路径
  ensurePathAvailability()
  // 生成NPC
  generateNpcs(npcCount.value, safeZone)
  // 确保NPC生成不会堵塞路径
  ensurePathAvailability()
  // 更新玩家初始位置
  updatePlayerPosition()
}

// 创建一个安全区域，该区域是围绕玩家当前位置的 5x5 网格
const createSafeZone = () => {
  const safeZone = new Set()
  for (let y = playerY.value - 2; y <= playerY.value + 2; y++) {
    for (let x = playerX.value - 2; x <= playerX.value + 2; x++) {
      // 确保坐标在有效的网格范围内
      if (y >= 0 && y < gridSize.value && x >= 0 && x < gridSize.value)
        safeZone.add(y * gridSize.value + x) // 将坐标转换为一维索引并添加到安全区域集合中
    }
  }
  // 返回安全区域集合
  return safeZone
}

// 确保地图中有可用路径，如果没有则重新生成障碍物和 NPC
const ensurePathAvailability = () => {
  while (!isPathAvailable()) {
    // 重新生成障碍物，排除在安全区域内的网格
    generateItems('obstacle', obstacleCount.value, createSafeZone())
    // 重新生成 NPC，排除在安全区域内的网格
    generateNpcs(npcCount.value, createSafeZone())
  }
}

// 使用 A* 算法检查从起点到终点的路径是否可用
const isPathAvailable = () => {
  // 起点坐标
  const start = [playerY.value, playerX.value]
  // 终点坐标
  const end = [49, 49]
  // 四个方向的移动方式
  const directions = [[0, 1], [1, 0], [0, -1], [-1, 0]]
  // 计算启发式函数（曼哈顿距离加上一个小权重）
  const heuristic = (a, b) => {
    const d1 = Math.abs(a[0] - b[0])
    const d2 = Math.abs(a[1] - b[1])
    return d1 + d2 + 0.1 * Math.min(d1, d2)
  }
  // 初始化 A* 算法所需的数据结构
  const openSet = new MinHeap() // 用于存储待检查的节点
  const cameFrom = new Map() // 记录路径
  const gScore = new Map() // 记录从起点到当前节点的实际代价
  const fScore = new Map() // 记录从起点到终点的估算代价
  gScore.set(start.toString(), 0)
  fScore.set(start.toString(), heuristic(start, end))
  openSet.add(start, fScore.get(start.toString()))
  while (!openSet.isEmpty()) {
    // 获取具有最低 f 值的节点
    const current = openSet.poll()
    // 如果当前节点是终点，则路径可用
    if (current[0] === 49 && current[1] === 49) return true
    for (const [dy, dx] of directions) {
      const newY = current[0] + dy
      const newX = current[1] + dx
      const neighbor = [newY, newX]
      // 确保邻居节点在有效的网格范围内且为空
      if (newY >= 0 && newY < gridSize.value && newX >= 0 && newX < gridSize.value &&
        grid.value[newY * gridSize.value + newX].type === 'empty') {
        const tentativeGScore = gScore.get(current.toString()) + 1
        // 如果找到更短的路径
        if (!gScore.has(neighbor.toString()) || tentativeGScore < gScore.get(neighbor.toString())) {
          cameFrom.set(neighbor.toString(), current)
          gScore.set(neighbor.toString(), tentativeGScore)
          const fScoreValue = tentativeGScore + heuristic(neighbor, end)
          fScore.set(neighbor.toString(), fScoreValue)
          openSet.add(neighbor, fScoreValue)
        }
      }
    }
  }
  // 如果没有找到路径，则返回 false
  return false
}

// 生成指定数量的障碍物，并确保生成后路径仍然可用
const generateItems = (type, count, excludeSet) => {
  // 将所有现有的障碍物转换为空
  grid.value.forEach(cell => {
    if (cell.type === type) cell.type = 'empty'
  })
  let placed = 0
  const arr = []
  // 用来存储每个钓鱼点周围的5x5区域
  const restrictedZone = new Set()
  while (placed < count) {
    const index = Math.floor(Math.random() * totalCells.value)
    const arrIndex = Math.floor(Math.random() * 25)
    // 检查是否新生成的位置在安全区域或禁区内，并且该格子为空
    if (!excludeSet.has(index) && !restrictedZone.has(index) && grid.value[index].type === 'empty') {
      grid.value[index].type = type
      placed++
      // 如果是钓鱼点，存储钓鱼点坐标，并生成其周围的5x5禁区
      if (type === 'fishing') {
        arr.push(index)
        store.fishingMap = arr
        // 将钓鱼点周围的5x5区域添加到禁区
        addToRestrictedZone(index, restrictedZone)
      }
      // 生成障碍物后立即检查路径是否可行
      if (!isPathAvailable()) {
        // 如果阻塞路径，回溯这个障碍物的生成
        grid.value[index].type = 'empty'
        placed--
      }
    }
  }
}

// 生成指定数量的 NPC，并确保生成后路径仍然可用
const generateNpcs = (count, excludeSet) => {
  // 将所有现有的 NPC 转换为空
  grid.value.forEach(cell => {
    if (cell.type === 'npc') cell.type = 'empty'
  })
  const npcs = player.value.npcs.length ? player.value.npcs : npc.npcNames()
  const isData = player.value.npcs.length
  let arr = []
  let placed = 0
  while (placed < count) {
    const index = Math.floor(Math.random() * totalCells.value)
    // 确保新生成的 NPC 不在安全区域内且为空
    if (!excludeSet.has(index) && grid.value[index].type === 'empty') {
      const favorability = isData ? npcs[placed].favorability : 0
      const lightness = calculateLightness(favorability)
      // 设置 NPC 的颜色
      grid.value[index].his = `hsl(340, 82%, ${lightness}%, 1)`
      grid.value[index].type = 'npc'
      const npcData = {
        lv: 144,
        name: isData ? npcs[placed].name : npcs[placed],
        position: index,
        favorability: favorability,
        reincarnation: 10
      }
      arr.push(npcData)
      player.value.npcs = arr
      placed++
      // 生成 NPC 后立即检查路径是否可行
      if (!isPathAvailable()) {
        // 如果阻塞路径，回溯这个 NPC 的生成
        grid.value[index].type = 'empty'
        placed--
      }
    }
  }
}

// 将目标的5x5范围改为禁区
const addToRestrictedZone = (index, restrictedZone) => {
  const gridX = index % gridSize.value
  const gridY = Math.floor(index / gridSize.value)
  // 遍历钓鱼点周围的5x5区域
  for (let y = gridY - 2; y <= gridY + 2; y++) {
    for (let x = gridX - 2; x <= gridX + 2; x++) {
      if (y >= 0 && y < gridSize.value && x >= 0 && x < gridSize.value) {
        // 将坐标转换为一维索引并添加到禁区集合中
        restrictedZone.add(y * gridSize.value + x)
      }
    }
  }
}

// 根据亲密度计算颜色亮度
const calculateLightness = (favorability) => {
  const maxFavorability = 1000
  const minLightness = 50 // 最暗
  const maxLightness = 76 // 最亮
  // 根据亲密度百分比计算亮度
  const percentage = favorability / maxFavorability
  return minLightness + (maxLightness - minLightness) * percentage
}

const harvestNpc = (item) => {
  ElMessageBox.confirm('与对方结为道侣有50%的概率失败, 失败后好感度会清空, 请问还想与对方结为道侣吗?', '结为道侣', {
    center: true,
    cancelButtonText: '取消',
    confirmButtonText: '确定',
  }).then(() => {
    const rand = isLucky(50)
    if (rand) {
      // 添加道侣
      player.value.wifes.push({
        name: item.name,
        level: 0,
        dodge: 0,
        attack: 10,
        health: 100,
        defense: 10,
        critical: 0,
        reincarnation: 0
      })
      gameNotifys({ title: '提示', message: '你成功邀请对方与你结为道侣', position: 'top-left' })
    } else {
      npcInfo.value.favorability = 0
      gameNotifys({ title: '提示', message: '对方拒绝了你的邀请, 好感度清空', position: 'top-left' })
    }
  }).catch(() => { })
}

// 礼物信息
const giftInfo = (item, index) => {
  ElMessageBox.confirm('', '赠送礼物', {
    center: true,
    message: `<div class="monsterinfo">
      <div class="monsterinfo-box">
        <p>名称: ${item.name}</p>
        <p>价格: ${item.price}</p>
        <p>增加好感度: ${item.plus}</p>
      </div>
    </div>`,
    lockScroll: false,
    cancelButtonText: '取消赠送',
    confirmButtonText: '立即赠送',
    dangerouslyUseHTMLString: true
  }).then(() => {
    if (item.price > player.value.props.money) {
      gameNotifys({ title: '赠送提示', message: '灵石不足, 赠送失败', position: 'top-left' })
      return
    }
    // 扣除灵石数量
    player.value.props.money -= item.price
    // 增加好感度
    npcInfo.value.favorability += item.plus
    // 增加传送符
    player.value.props.flying += index
    // 增加情缘点
    player.value.props.qingyuan += index
    gameNotifys({ title: '赠送提示', message: `赠送成功, ${npcInfo.value.name}对你的好感度增加了, 并赠与了你${index}张传送符和${index}点情缘`, position: 'top-left' })
  }).catch(() => { })
}

// 地图信息
const gridInfo = (index, item) => {
  // 如果坐标不是空地
  if (item.type != 'empty') return
  const x = index % gridSize.value
  const y = Math.floor(index / gridSize.value)
  ElMessageBox.confirm('地图坐标信息', '地图坐标信息', {
    center: true,
    message: `<div class="monsterinfo">
      <div class="monsterinfo-box">
        <p>X轴: ${x}</p>
        <p>Y轴: ${y}</p>
      </div>
    </div>`,
    lockScroll: false,
    cancelButtonText: '取消传送',
    confirmButtonText: '立即传送',
    dangerouslyUseHTMLString: true
  }).then(() => {
    if (!player.value.props.flying) {
      gameNotifys({ title: '传送提示', message: '传送失败, 传送符不足', position: 'top-left' })
      return
    }
    playerY.value = y
    playerX.value = x
    updatePlayerPosition()
    gameNotifys({ title: '传送提示', message: '传送成功', position: 'top-left' })
  }).catch(() => { })
}

// 更新玩家在地图上的位置
const updatePlayerPosition = () => {
  grid.value.forEach(cell => (cell.type === 'player' ? cell.type = 'empty' : cell.type))
  const playerIndex = playerY.value * gridSize.value + playerX.value
  grid.value[playerIndex].type = 'player'
  // 更新地图数据
  store.mapData = {
    y: playerY.value,
    x: playerX.value,
    map: grid.value
  }
  // 20%概率遇怪
  const rand = isLucky(20)
  if (rand && playerIndex != 0) {
    // 玩家境界
    let level = player.value.level == 0 ? 1 : player.value.level
    // 怪物难度根据玩家最高境界 + 转生次数
    const monsterLv = level * player.value.reincarnation + level
    // 添加怪物数据
    store.monster = {
      // 名称
      name: monster.monster_Names(monsterLv),
      // 气血
      health: monster.monster_Health(monsterLv),
      // 攻击
      attack: monster.monster_Attack(monsterLv),
      // 防御
      defense: monster.monster_Defense(monsterLv),
      // 闪避率
      dodge: monster.monster_Criticalhitrate(monsterLv),
      // 暴击
      critical: monster.monster_Criticalhitrate(monsterLv)
    }
    // 跳转对战
    router.push('/explore')
  }
  // 每次更新玩家位置后调用
  if (playerIndex != 0) updateScroll(playerIndex)
}

// 计算玩家指定方向是否有NPC或障碍物
const checkDirection = (direction) => {
  const directions = {
    up: (playerY.value - 1) * gridSize.value + playerX.value,
    down: (playerY.value + 1) * gridSize.value + playerX.value,
    left: playerY.value * gridSize.value + (playerX.value - 1),
    right: playerY.value * gridSize.value + (playerX.value + 1)
  }
  const index = directions[direction]
  return ['obstacle', 'fishing', 'npc'].includes(grid.value[index]?.type)
}

// 根据方向移动玩家
const move = (direction) => {
  let newX = playerX.value
  let newY = playerY.value
  direction = typeof direction === 'string' ? direction : direction.key
  switch (direction) {
    case 'q':
      goHome()
      return
    case 'w':
    case 'up':
    case 'ArrowUp':
      if (newY > 0) {
        newY--
        goPlayerXY(newY, newX)
      }
      break
    case 's':
    case 'down':
    case 'ArrowDown':
      if (newY < gridSize.value - 1) {
        newY++
        goPlayerXY(newY, newX)
      }
      break
    case 'a':
    case 'left':
    case 'ArrowLeft':
      if (newX > 0) {
        newX--
        goPlayerXY(newY, newX)
      }
      break
    case 'd':
    case 'right':
    case 'ArrowRight':
      if (newX < gridSize.value - 1) {
        newX++
        goPlayerXY(newY, newX)
      }
      break
    case 'e':
      if (!isNpc.value && !isFishing.value) return
      if (isNpc.value) talkToNpc()
      else startGame()
      return
    default:
      return
  }
}

const goPlayerXY = (newY, newX) => {
  const newIndex = newY * gridSize.value + newX
  // 仅在目标位置为空地时移动玩家
  if (grid.value[newIndex].type === 'empty') {
    playerX.value = newX
    playerY.value = newY
  }
  updatePlayerPosition()
}

// 滚动条自动滚动到玩家所在行
const updateScroll = (playerIndex) => {
  const playerCell = cellRefs.value[playerIndex]
  if (playerCell) {
    store.mapScroll = playerIndex
    playerCell.scrollIntoView({ block: 'center', inline: 'center', behavior: 'smooth' })
  }
}

// 找到并与附近的NPC对话
const talkToNpc = () => {
  npcShow.value = true
  npcInfo.value = player.value.npcs.find(npc => nearbyIndices.value.includes(npc.position))
}

// 概率计算
const isLucky = (probability) => {
  // 生成一个0到100之间的随机数
  const randomValue = Math.random() * 100
  // 判断随机数是否小于等于传入的概率值
  return randomValue < probability
}

// 判断玩家是否已当前NPC结为道侣
const isHaveWife = (name) => {
  if (name) return player.value.wifes.some(item => item.name === name)
}

// 钓鱼
const moveButton = () => {
  // 平滑向上移动
  smoothMoveUp()
  // 检查碰撞
  checkCollision()
}

// 启动长按上浮逻辑
const startFishing = () => {
  if (!holdingId.value) {
    moveButton()
    holdingId.value = setInterval(moveButton.value, 100)
  }
}

// 停止上浮并启动自动下滑
const stopFishing = () => {
  if (holdingId.value) {
    clearInterval(holdingId.value)
    holdingId.value = null
  }
}

// 移动鱼的位置
const moveParentContainer = () => {
  if (!fishingShow.value) return
  if (!movingContainer.value) return
  // 移动速度
  const speed = 3000
  // 最大Y轴移动范围
  const maxY = outerContainer.value?.clientHeight - movingContainer.value?.clientHeight
  const move = () => {
    // 如果时间已到，停止移动
    if (fishing.value.timeExpired) {
      // 清空所有定时
      clearAllTiming()
      return
    }
    // 随机生成新的Y轴位置
    const newY = Math.random() * maxY
    // 设置新的位置
    movingContainer.value.style.transition = `top ${speed}ms ease`
    movingContainer.value.style.top = `${newY}px`
    // 在动画结束后调用下一次移动
    setTimeout(() => {
      if (!fishing.value.timeExpired) {
        fishing.value.moveParentContainerId = requestAnimationFrame(move)
      }
    }, speed)
  }
  // 启动移动
  move()
}

// 鱼钩自动向下移动
const autoMoveDown = () => {
  const step = () => {
    const outerHeight = outerContainer.value?.clientHeight
    const innerHeight = innerContainer.value?.clientHeight
    // 检查钓鱼的容器是否触底
    if (fishing.value.innerPosition <= 0) {
      // 如果触底且没有重叠，则扣除分数和进度
      if (!fishing.value.overlap) {
        fishing.value.score = Math.max(fishing.value.score - 0.2, 0)
        updateProgressBar()
      }
    } else {
      // 向下移动
      fishing.value.innerPosition -= 1
      innerContainer.value.style.bottom = `${fishing.value.innerPosition}px`
      // 检查碰撞
      checkCollision()
    }
    // 请求下一帧动画
    fishing.value.moveDownAnimationId = requestAnimationFrame(step)
  }
  // 启动自动向下移动
  fishing.value.moveDownAnimationId = requestAnimationFrame(step)
}

// 平滑向上移动鱼钩的位置
const smoothMoveUp = () => {
  // 目标位置
  const targetPosition = Math.min(fishing.value.innerPosition + 10, outerContainer.value?.clientHeight - innerContainer.value?.clientHeight)
  const step = () => {
    if (fishing.value.innerPosition < targetPosition) {
      // 向上移动
      fishing.value.innerPosition += 2
      innerContainer.value.style.bottom = `${fishing.value.innerPosition}px`
      // 请求下一帧动画
      fishing.value.moveUpAnimationId = requestAnimationFrame(step)
    }
  }
  // 启动平滑向上移动
  fishing.value.moveUpAnimationId = requestAnimationFrame(step)
}

// 检查碰撞
const checkCollision = () => {
  // 如果游戏结束或时间到，停止检查
  if (fishing.value.gameOver || fishing.value.timeExpired) return
  // 获取鱼的矩形区域
  const movingRect = movingContainer.value?.getBoundingClientRect()
  // 获取鱼钩的矩形区域
  const innerRect = innerContainer.value?.getBoundingClientRect()
  // 判断鱼钩是否触底
  const isTouchingBottom = fishing.value.innerPosition <= 0
  // 判断是否有重叠
  const isOverlap = !(
    movingRect.bottom < innerRect.top ||  // 鱼钩底部在鱼上方
    movingRect.top > innerRect.bottom ||  // 鱼钩顶部在鱼下方
    movingRect.right < innerRect.left ||  // 鱼钩右边在鱼左边
    movingRect.left > innerRect.right     // 鱼钩左边在鱼右边
  )
  // 根据鱼钩的触底和重叠状态更新分数
  fishing.value.score = isOverlap ? Math.min(fishing.value.score + 0.1, 100) : isTouchingBottom ? Math.max(fishing.value.score - 0.2, 0) : Math.max(fishing.value.score - 0.2, 0)
  // 更新分数后更新进度条
  updateProgressBar(fishing.value.score)
  // 更新重叠状态
  fishing.value.overlap = isOverlap
}

// 更新进度条
const updateProgressBar = () => {
  // 最大分数
  const maxScore = 100
  // 计算进度百分比
  const progressPercentage = Math.min((fishing.value.score / maxScore) * 100, 100)
  if (fishing.value.score >= maxScore && !fishing.value.gameOver) {
    fishing.value.gameOver = true
    const fishInfo = getRandomFish()
    ElMessageBox.confirm(fishInfo.description, fishInfo.name, {
      center: true,
      showClose: false,
      lockScroll: false,
      showCancelButton: false,
      closeOnClickModal: false,
      confirmButtonText: '卖掉这条鱼'
    }).then(() => {
      // 结束游戏
      endGame()
      // 增加灵石数量
      player.value.props.money += fishInfo.price
      gameNotifys({ title: '提示', message: `你获得了${fishInfo.price}灵石`, position: 'top-left' })
    }).catch(() => { })
  }
  // 更新进度条文字
  fishing.value.progressText = Math.round(progressPercentage)
  // 更新进度条高度
  fishing.value.progressPercentage = `${progressPercentage}%`
}

// 启动计时器
const startTimer = () => {
  const timer = setInterval(() => {
    fishing.value.timeLeft--
    // 倒计时结束
    if (fishing.value.timeLeft == 0) {
      ElMessageBox.confirm('钓鱼失败，鱼溜走了！', '提示', {
        center: true,
        lockScroll: false,
        showCancelButton: false,
        confirmButtonText: '确定',
        dangerouslyUseHTMLString: true
      }).then(() => endGame()).catch(() => endGame())
    }
  }, 1000)
  fishing.value.timerIds.push(timer)
}

// 结束游戏
const endGame = () => {
  fishingShow.value = false
  // 清空所有定时
  clearAllTiming()
  // 移除钓鱼点
  const index = store.fishingMap.find(index => nearbyIndices.value.includes(index))
  grid.value[index].type = 'empty'
  // 结束所有动画
  cancelAnimationFrame(fishing.value.moveUpAnimationId)
  cancelAnimationFrame(fishing.value.moveDownAnimationId)
  cancelAnimationFrame(fishing.value.moveParentContainerId)
  // 重置钓鱼数据
  resetFishingData(true)
}

// 开始游戏
const startGame = () => {
  fishingShow.value = true
  resetFishingData(false)
  nextTick(() => moveParentContainer())
  autoMoveDown()
  startTimer()
}

// 重置所有钓鱼数据
const resetFishingData = (bool) => {
  // 分数
  fishing.value.score = 0
  // 是否重叠
  fishing.value.overlap = false
  // 剩余时间
  fishing.value.timeLeft = 60
  // 游戏结束
  fishing.value.gameOver = bool
  // 禁用按钮
  fishing.value.disabled = bool
  // 时间结束
  fishing.value.timeExpired = bool
  // 内部容器位置
  fishing.value.innerPosition = 0
  if (!bool) {
    // 进度
    fishing.value.progressText = 0
    // 更新进度条高度
    fishing.value.progressPercentage = '0%'
  }
}

// 产出钓到鱼的信息
const getRandomFish = () => {
  const random = Math.random()
  let cumulativeProbability = 0
  const fishList = [
    { name: '金鳞鲤鱼', price: 100, description: '金色鳞片的鲤鱼，象征着好运和财富。', probability: 0.25 },
    { name: '青铜鲑鱼', price: 150, description: '体型健硕的鲑鱼，常出现在清凉的河流中。', probability: 0.2 },
    { name: '冰川鳟鱼', price: 200, description: '来自寒冷冰川地区的鳟鱼，肉质鲜美。', probability: 0.18 },
    { name: '红尾锦鲤', price: 300, description: '具有漂亮红色尾巴的锦鲤，观赏价值极高。', probability: 0.15 },
    { name: '银鳍鲢鱼', price: 80, description: '常见于淡水湖泊，银色鳍片在水中闪闪发光。', probability: 0.3 },
    { name: '巨口鲶鱼', price: 180, description: '以其巨大的嘴巴和强劲的咬力著称，肉质肥美。', probability: 0.17 },
    { name: '幽蓝灯鱼', price: 250, description: '深海中发出幽蓝光芒的小型鱼类，具有神秘色彩。', probability: 0.14 },
    { name: '翡翠剑鱼', price: 500, description: '剑形的鱼嘴和翡翠般的色彩让它成为海中战士。', probability: 0.08 },
    { name: '斑点鲈鱼', price: 90, description: '遍布斑点的鲈鱼，适应力极强，分布广泛。', probability: 0.28 },
    { name: '火焰红鱼', price: 400, description: '拥有火焰般红色鳞片的稀有鱼类，价格不菲。', probability: 0.1 },
    { name: '雷电鳗鱼', price: 350, description: '全身带有微弱电流的鳗鱼，捕食时能够电击猎物。', probability: 0.12 },
    { name: '黑甲鲨', price: 600, description: '身披黑色盔甲般鳞片的鲨鱼，充满攻击性。', probability: 0.06 },
    { name: '水晶鲤鱼', price: 120, description: '鳞片透明的鲤鱼，在阳光下晶莹剔透。', probability: 0.22 },
    { name: '紫鳍金枪鱼', price: 700, description: '金枪鱼中的顶级品种，因其紫色鳍而得名。', probability: 0.05 },
    { name: '珍珠贝鱼', price: 220, description: '外形酷似贝壳的鱼类，藏有珍珠般的光泽。', probability: 0.16 },
    { name: '蓝鲸鱼苗', price: 1000, description: '蓝鲸的幼年鱼苗，体积虽小但潜力巨大。', probability: 0.03 },
    { name: '彩虹鳟鱼', price: 280, description: '彩虹般斑斓的鳟鱼，深受垂钓者喜爱。', probability: 0.13 },
    { name: '毒针河豚', price: 150, description: '体型小但带有剧毒的河豚，需小心处理。', probability: 0.2 },
    { name: '黄金海马', price: 500, description: '稀有的海马品种，金黄色的身体闪闪发光。', probability: 0.08 },
    { name: '深海魔鱼', price: 800, description: '生活在深海中的巨大鱼类，外表极具威胁性。', probability: 0.04 }
  ]
  // 遍历鱼类列表，根据概率抽取鱼
  for (const fish of fishList) {
    // 累加概率
    cumulativeProbability += fish.probability
    if (random < cumulativeProbability) {
      // 一旦随机数小于当前累加的概率，返回该鱼
      return fish
    }
  }
  // 如果没有匹配到
  return null
}

onMounted(() => {
  if (mapData.value.map.length) {
    // 恢复地图数据
    grid.value = mapData.value.map
    // 玩家所在Y轴
    playerY.value = mapData.value.y
    // 玩家所在X轴
    playerX.value = mapData.value.x
  } else {
    // 初始化地图数据
    initializeGrid()
  }
  // 恢复玩家滚动条位置
  if (store.mapScroll) nextTick(() => updateScroll(store.mapScroll))
  // 添加键盘监听
  window.addEventListener('keydown', move)
})

onUnmounted(() => {
  // 清空所有定时
  clearAllTiming()
  // 移除键盘监听
  window.removeEventListener('keydown', move)
})
</script>

<style scoped>
.map {
  max-width: 770px;
  height: 500px;
  overflow: auto;
}

.grid-container {
  display: grid;
  grid-template-columns: repeat(50, 12px);
  grid-template-rows: repeat(50, 12px);
  gap: 1px;
  width: fit-content;
  margin: 0 auto;
}

.grid-item {
  width: 12px;
  height: 12px;
  background-color: #ddd;
}

.player {
  background-color: var(--el-color-success);
}

.fishing {
  background-color: var(--el-color-primary);
}

.obstacle {
  background-color: var(--el-color-warning);
}

.npc {
  background-color: var(--el-color-pink-light);
}

.empty {
  background-color: var(--el-color-info-light-8);
}

.x-0.y-0 {
  border-top-left-radius: 2px;
}

.x-49.y-0 {
  border-top-right-radius: 2px;
}

.x-0.y-49 {
  border-bottom-left-radius: 2px;
}

.x-49.y-49 {
  border-bottom-right-radius: 2px;
}

.controls {
  text-align: center;
  margin-top: 20px;
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  grid-template-rows: auto auto auto;
  width: 200px;
  margin: 20px auto;
}

.center-buttons {
  display: flex;
  justify-content: space-between;
  grid-column: span 3;
}

.controls>button,
.center-buttons>button {
  padding: 10px;
  margin: 5px;
  font-size: 16px;
}

.legend {
  display: flex;
  justify-content: center;
  margin-bottom: 10px;
}

.legend-item {
  display: flex;
  align-items: center;
  margin: 0 10px;
}

@media only screen and (max-width: 768px) {
  .legend {
    flex-wrap: wrap;
  }

  .legend-item {
    width: 33.333%;
    margin: 10px 0 0 0;
    justify-content: center;
  }
}

.color-box {
  width: 12px;
  height: 12px;
  margin-right: 5px;
  border-radius: 2px;
}

.wife-box {
  padding: 0 5px;
}

.attribute-box {
  display: flex;
  flex-wrap: wrap;
}

.attribute {
  width: 100%;
  margin: 4px;
}

.click-box {
  padding: 0 5px;
  margin-top: 10px;
  display: flex;
}

.click-box button {
  margin-top: 10px;
  width: 100%;
}

.gift-box {
  display: flex;
  flex-wrap: wrap;
  width: 100%;
  margin-top: 20px;
}

.gift-item {
  width: calc(50% - 8px);
  margin-top: 10px;
  display: grid;
  padding: 0 4px;
}

/* 钓鱼 */
.game-container {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.time {
  font-size: 15px;
  margin-bottom: 10px;
}

.outer-wrapper {
  display: flex;
  align-items: center;
}

.progress-bar-container {
  width: 40px;
  height: 400px;
  margin-right: 10px;
  border: 2px solid var(--el-border-color);
  border-radius: 5px;
  position: relative;
  overflow: hidden;
}

.progress-bar {
  width: 100%;
  height: 0%;
  background-color: var(--el-color-success);
  position: absolute;
  bottom: 0;
  transition: height 0.3s ease;
  display: flex;
  justify-content: center;
  align-items: flex-end;
}

.progress-text {
  color: var(--el-text-color-primary);
  font-size: 13px;
  font-weight: bold;
  padding-bottom: 5px;
  position: relative;
  top: 50%;
  bottom: 0;
  text-align: center;
  transform: translateY(-50%);
}

.outer-container {
  width: 50px;
  height: 400px;
  position: relative;
  overflow: hidden;
  border: 2px solid var(--el-text-color-primary);
  border-radius: 5px;
}

.moving-container {
  width: 50px;
  height: 50px;
  /* background-color: lightblue; */
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
}

.moving-container svg {
  width: 36px;
  height: 36px;
}

.inner-container {
  position: absolute;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  transition: bottom 0.1s ease;
}

.inner-container svg {
  width: 36px;
  height: 36px;
  transform: scaleX(-1);
}

.button {
  margin-top: 10px;
  padding: 10px 20px;
  font-size: 16px;
  width: 100%;
}

/* 移动按钮 */
.shortcutKeys {
  color: rgba(169, 169, 169, 0.4);
  margin-left: 2px;
}

/* 钓鱼 */
.diaoyu-shortcutKeys {
  display: none;
}

@media only screen and (max-width: 768px) {
  .shortcutKeys {
    display: none;
  }

  .diaoyu-shortcutKeys {
    color: rgba(169, 169, 169, 0.4);
    margin-left: 2px;
  }
}
</style>

<style>
/* 钓鱼动画 */
@keyframes wobble {
  0% {
    transform: translateX(-50%) rotate(0);
  }

  25% {
    transform: translateX(-50%) rotate(-5deg);
  }

  50% {
    transform: translateX(-50%) rotate(0);
  }

  75% {
    transform: translateX(-50%) rotate(5deg);
  }

  100% {
    transform: translateX(-50%) rotate(0);
  }
}
</style>